package top.broncho.mvvm.permission

import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.annotation.MainThread
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager

import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

sealed class PermissionResult

object Granted : PermissionResult()

class ShowRational(val list: List<String>) : PermissionResult()

class DeniedPermanently(val list: List<String>) : PermissionResult()

/**
 * @param permissions vararg of all the permissions for request.
 * @param requestBlock block constructing [PermissionResult] object for permission request.
 */
inline fun AppCompatActivity.requestPermissions(
    vararg permissions: String,
    crossinline requestBlock: (PermissionResult) -> Unit
) {
    PermissionManager.requestPermissions(this, *permissions) { requestBlock.invoke(it) }
}

/**
 * @param permissions vararg of all the permissions for request.
 * @param requestBlock block constructing [PermissionResult] object for permission request.
 */
inline fun Fragment.requestPermissions(
    vararg permissions: String,
    crossinline requestBlock: (PermissionResult) -> Unit
) {
    PermissionManager.requestPermissions(this, *permissions) { requestBlock.invoke(it) }
}


suspend inline fun AppCompatActivity.requestPermissions(vararg permissions: String) =
    suspendCoroutine<PermissionResult> { continuation ->
        PermissionManager.requestPermissions(this, *permissions) {
            continuation.resume(it)
        }
    }

suspend inline fun Fragment.requestPermissions(vararg permissions: String) =
    suspendCoroutine<PermissionResult> { continuation ->
        PermissionManager.requestPermissions(this, *permissions) {
            continuation.resume(it)
        }
    }

/**
 * A simple [Fragment] subclass.
 */
class PermissionManager : Fragment() {
    private var resultCallback: ((PermissionResult) -> Unit)? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        retainInstance = true
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        if (grantResults.isNotEmpty() &&
            grantResults.all { it == PackageManager.PERMISSION_GRANTED }
        ) {
            onPermissionResult(Granted)
        } else if (permissions.any { shouldShowRequestPermissionRationale(it) }) {
            onPermissionResult(
                ShowRational(
                    permissions.filterIndexed { index, _ ->
                        grantResults[index] == PackageManager.PERMISSION_DENIED
                    }
                )
            )
        } else {
            onPermissionResult(
                DeniedPermanently(
                    permissions.filterIndexed { index, _ ->
                        grantResults[index] == PackageManager.PERMISSION_DENIED
                    }
                ))
        }
    }

    fun requestPermissions(resultCallback: (PermissionResult) -> Unit, vararg permissions: String) {
        this.resultCallback = resultCallback
        val notGranted = permissions.filter {
            ContextCompat.checkSelfPermission(
                requireActivity(),
                it
            ) != PackageManager.PERMISSION_GRANTED
        }.toTypedArray()

        when {
            notGranted.isEmpty() -> onPermissionResult(Granted)
            else -> requestPermissions(notGranted, PERMISSIONS_REQUEST_CODE)
        }
    }

    private fun onPermissionResult(permissionResult: PermissionResult) {
        resultCallback?.invoke(permissionResult)

    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        resultCallback = null
    }

    override fun onDestroy() {
        super.onDestroy()
        resultCallback = null
    }

    companion object {

        private const val TAG = "PermissionManager"
        private const val PERMISSIONS_REQUEST_CODE = 42


        /**
         * A static factory inline method to request permission for activity.
         *
         * @param activity an instance of [AppCompatActivity]
         * @param permissions vararg of all permissions for request
         * @param resultCallback [(PermissionResult) -> Unit] block for permission request
         *
         */
        @JvmStatic
        @MainThread
        fun requestPermissions(
            activity: AppCompatActivity,
            vararg permissions: String,
            resultCallback: (PermissionResult) -> Unit
        ) {
            getPermissionsFragment(activity.supportFragmentManager).apply {
                requestPermissions(resultCallback, *permissions)
            }
        }

        /**
         * A static factory inline method to request permission for fragment.
         *
         * @param fragment an instance of [Fragment]
         * @param permissions vararg of all permissions for request
         * @param resultCallback [(PermissionResult) -> Unit] block for permission request
         *
         */
        @JvmStatic
        @MainThread
        fun requestPermissions(
            fragment: Fragment,
            vararg permissions: String,
            resultCallback: (PermissionResult) -> Unit
        ) {
            getPermissionsFragment(fragment.childFragmentManager).apply {
                requestPermissions(resultCallback, *permissions)
            }
        }

        private fun getPermissionsFragment(fragmentManager: FragmentManager): PermissionManager {
            var fragment = fragmentManager.findFragmentByTag(TAG)
            if (fragment != null) {
                fragment = fragment as PermissionManager
            } else {
                fragment = PermissionManager()
                fragmentManager.beginTransaction().add(fragment, TAG).commitNow()
            }
            return fragment
        }

    }

}